aimeat 1.16.2 → 1.18.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 (281) hide show
  1. package/dist/.env.example +6 -1
  2. package/dist/locales/en.json +166 -4
  3. package/dist/locales/fi.json +166 -4
  4. package/dist/public/components/Card.js +17 -3
  5. package/dist/public/components/Collapsible.js +39 -0
  6. package/dist/public/components/CopyButton.js +55 -24
  7. package/dist/public/components/DataTable.js +57 -0
  8. package/dist/public/components/EmptyState.js +28 -0
  9. package/dist/public/components/KeyValueRow.js +37 -0
  10. package/dist/public/components/Modal.js +22 -5
  11. package/dist/public/components/Pagination.js +70 -0
  12. package/dist/public/components/SearchBar.js +44 -0
  13. package/dist/public/components/StatusDot.js +29 -0
  14. package/dist/public/components/TagList.js +53 -0
  15. package/dist/public/components/ToggleSwitch.js +28 -0
  16. package/dist/public/components/index.js +41 -1
  17. package/dist/public/css/components/tags.css +24 -0
  18. package/dist/public/css/style-testbed.css +135 -0
  19. package/dist/public/css/theme.css +556 -34
  20. package/dist/public/css/views/admin-agent-integration.css +2 -33
  21. package/dist/public/css/views/admin.css +730 -686
  22. package/dist/public/css/views/agents-detail.css +402 -14
  23. package/dist/public/css/views/help.css +2 -1
  24. package/dist/public/css/views/hobbies.css +53 -33
  25. package/dist/public/css/views/marketplace.css +73 -60
  26. package/dist/public/css/views/openclaw.css +12 -6
  27. package/dist/public/css/views/portal-classic.css +34 -26
  28. package/dist/public/css/views/portal-dev.css +41 -32
  29. package/dist/public/css/views/portal.css +14 -18
  30. package/dist/public/css/views/profile.css +252 -164
  31. package/dist/public/css/views/public-knowledge-viewer.css +18 -6
  32. package/dist/public/css/views/scheduler.css +100 -0
  33. package/dist/public/js/format.js +41 -0
  34. package/dist/public/js/services/agent-tasks.js +14 -1
  35. package/dist/public/js/services/generator-spec-validate.js +5 -1
  36. package/dist/public/js/services/schedules.js +53 -0
  37. package/dist/public/js/style-testbed.js +170 -0
  38. package/dist/public/js/theme.js +114 -0
  39. package/dist/public/spa.html +74 -16
  40. package/dist/public/style-testbed.html +420 -0
  41. package/dist/public/views/admin/agent-integration-tab.js +23 -38
  42. package/dist/public/views/admin/agent-tasks-tab.js +14 -21
  43. package/dist/public/views/admin/chat-instances-tab.js +1 -1
  44. package/dist/public/views/admin/config-tab.js +7 -2
  45. package/dist/public/views/admin/cortex-tab.js +20 -14
  46. package/dist/public/views/admin/csm-tab.js +22 -18
  47. package/dist/public/views/admin/directory-tab.js +3 -3
  48. package/dist/public/views/admin/email-tab.js +23 -28
  49. package/dist/public/views/admin/federation-tab.js +15 -5
  50. package/dist/public/views/admin/generator-debug-tab.js +10 -8
  51. package/dist/public/views/admin/genesis-tab.js +4 -4
  52. package/dist/public/views/admin/ghii-tab.js +3 -3
  53. package/dist/public/views/admin/knowledge-tab.js +15 -5
  54. package/dist/public/views/admin/matching-tab.js +3 -3
  55. package/dist/public/views/admin/memory-tab.js +16 -20
  56. package/dist/public/views/admin/msm-tab.js +30 -17
  57. package/dist/public/views/admin/overview-tab.js +7 -7
  58. package/dist/public/views/admin/packages-tab.js +10 -9
  59. package/dist/public/views/admin/portal-tab.js +14 -4
  60. package/dist/public/views/admin/prompts-tab.js +5 -5
  61. package/dist/public/views/admin/push-tab.js +15 -4
  62. package/dist/public/views/admin/realtime-tab.js +3 -3
  63. package/dist/public/views/admin/services-tab.js +18 -27
  64. package/dist/public/views/admin/shared.js +48 -58
  65. package/dist/public/views/admin/sharing-groups-tab.js +5 -10
  66. package/dist/public/views/admin/stats-tab.js +3 -3
  67. package/dist/public/views/guides.js +9 -13
  68. package/dist/public/views/help.js +3 -24
  69. package/dist/public/views/hobbies.js +47 -41
  70. package/dist/public/views/marketplace.js +42 -34
  71. package/dist/public/views/openclaw.js +0 -1
  72. package/dist/public/views/portal-classic.js +4 -15
  73. package/dist/public/views/portal-dev.js +28 -30
  74. package/dist/public/views/portal.js +16 -21
  75. package/dist/public/views/portfolio.js +11 -12
  76. package/dist/public/views/profile/access-tab.js +250 -1
  77. package/dist/public/views/profile/agents/agent-card.js +75 -4
  78. package/dist/public/views/profile/agents/tab-activity.js +15 -13
  79. package/dist/public/views/profile/agents/tab-agent-config.js +60 -6
  80. package/dist/public/views/profile/agents/tab-integration.js +37 -60
  81. package/dist/public/views/profile/agents/tab-messages.js +10 -1
  82. package/dist/public/views/profile/agents/tab-quality.js +25 -19
  83. package/dist/public/views/profile/agents/tab-schedules.js +110 -0
  84. package/dist/public/views/profile/agents-activity-subtab.js +4 -4
  85. package/dist/public/views/profile/agents-services-subtab.js +5 -4
  86. package/dist/public/views/profile/agents-tab.js +141 -37
  87. package/dist/public/views/profile/agents-tasks-subtab.js +221 -26
  88. package/dist/public/views/profile/calibrator-batch.js +5 -11
  89. package/dist/public/views/profile/calibrator-tab.js +4 -9
  90. package/dist/public/views/profile/chat-sessions-tab.js +9 -5
  91. package/dist/public/views/profile/email-tab.js +2 -2
  92. package/dist/public/views/profile/extensions-tab.js +27 -22
  93. package/dist/public/views/profile/federation-tab.js +4 -1
  94. package/dist/public/views/profile/foundry-detail.js +22 -23
  95. package/dist/public/views/profile/foundry-tab.js +12 -13
  96. package/dist/public/views/profile/generator-dashboard/DebugPanel.js +4 -7
  97. package/dist/public/views/profile/generator-dashboard/use-dashboard-core.js +4 -1
  98. package/dist/public/views/profile/generator-dashboard/use-edit-mode.js +3 -2
  99. package/dist/public/views/profile/generator-detail.js +26 -30
  100. package/dist/public/views/profile/generator-tab.js +5 -5
  101. package/dist/public/views/profile/knowledge-tab.js +12 -12
  102. package/dist/public/views/profile/landing-page.js +100 -149
  103. package/dist/public/views/profile/memory-tab.js +15 -11
  104. package/dist/public/views/profile/node-stats-tab.js +3 -3
  105. package/dist/public/views/profile/nodes-tab.js +10 -3
  106. package/dist/public/views/profile/notifications-tab.js +3 -3
  107. package/dist/public/views/profile/packages-tab.js +14 -14
  108. package/dist/public/views/profile/scheduler-tab.js +340 -0
  109. package/dist/public/views/profile/services-tab.js +1 -1
  110. package/dist/public/views/profile/shared.js +16 -12
  111. package/dist/public/views/profile/wallet-tab.js +27 -50
  112. package/dist/public/views/profile/work-tab.js +29 -31
  113. package/dist/public/views/profile.js +24 -0
  114. package/dist/public/views/public-knowledge-viewer.js +9 -18
  115. package/dist/src/auth/middleware.d.ts +3 -2
  116. package/dist/src/auth/middleware.d.ts.map +1 -1
  117. package/dist/src/auth/middleware.js +78 -3
  118. package/dist/src/auth/middleware.js.map +1 -1
  119. package/dist/src/cli/init-wizard.d.ts.map +1 -1
  120. package/dist/src/cli/init-wizard.js +13 -1
  121. package/dist/src/cli/init-wizard.js.map +1 -1
  122. package/dist/src/config.d.ts +6 -0
  123. package/dist/src/config.d.ts.map +1 -1
  124. package/dist/src/config.js +12 -1
  125. package/dist/src/config.js.map +1 -1
  126. package/dist/src/generated/api-types.d.ts +708 -8
  127. package/dist/src/generated/api-types.d.ts.map +1 -1
  128. package/dist/src/mcp/agent-schedules.d.ts +23 -0
  129. package/dist/src/mcp/agent-schedules.d.ts.map +1 -0
  130. package/dist/src/mcp/agent-schedules.js +175 -0
  131. package/dist/src/mcp/agent-schedules.js.map +1 -0
  132. package/dist/src/mcp/annotations.d.ts.map +1 -1
  133. package/dist/src/mcp/annotations.js +6 -0
  134. package/dist/src/mcp/annotations.js.map +1 -1
  135. package/dist/src/mcp/catalog/definitions.d.ts.map +1 -1
  136. package/dist/src/mcp/catalog/definitions.js +55 -0
  137. package/dist/src/mcp/catalog/definitions.js.map +1 -1
  138. package/dist/src/mcp/catalog/scopes.d.ts.map +1 -1
  139. package/dist/src/mcp/catalog/scopes.js +12 -0
  140. package/dist/src/mcp/catalog/scopes.js.map +1 -1
  141. package/dist/src/mcp/catalog/surfaces.d.ts.map +1 -1
  142. package/dist/src/mcp/catalog/surfaces.js +2 -0
  143. package/dist/src/mcp/catalog/surfaces.js.map +1 -1
  144. package/dist/src/mcp/index.d.ts.map +1 -1
  145. package/dist/src/mcp/index.js +2 -0
  146. package/dist/src/mcp/index.js.map +1 -1
  147. package/dist/src/models/agent-task-schemas.d.ts +6 -0
  148. package/dist/src/models/agent-task-schemas.d.ts.map +1 -1
  149. package/dist/src/models/agent-task-schemas.js +6 -0
  150. package/dist/src/models/agent-task-schemas.js.map +1 -1
  151. package/dist/src/models/schemas.d.ts +1 -0
  152. package/dist/src/models/schemas.d.ts.map +1 -1
  153. package/dist/src/models/schemas.js +4 -0
  154. package/dist/src/models/schemas.js.map +1 -1
  155. package/dist/src/routes/access-tokens.d.ts +17 -0
  156. package/dist/src/routes/access-tokens.d.ts.map +1 -0
  157. package/dist/src/routes/access-tokens.js +164 -0
  158. package/dist/src/routes/access-tokens.js.map +1 -0
  159. package/dist/src/routes/agent-tasks.d.ts +2 -0
  160. package/dist/src/routes/agent-tasks.d.ts.map +1 -1
  161. package/dist/src/routes/agent-tasks.js +118 -23
  162. package/dist/src/routes/agent-tasks.js.map +1 -1
  163. package/dist/src/routes/agents.d.ts.map +1 -1
  164. package/dist/src/routes/agents.js +46 -0
  165. package/dist/src/routes/agents.js.map +1 -1
  166. package/dist/src/routes/ai.d.ts +6 -9
  167. package/dist/src/routes/ai.d.ts.map +1 -1
  168. package/dist/src/routes/ai.js +26 -175
  169. package/dist/src/routes/ai.js.map +1 -1
  170. package/dist/src/routes/apps.d.ts.map +1 -1
  171. package/dist/src/routes/apps.js +104 -34
  172. package/dist/src/routes/apps.js.map +1 -1
  173. package/dist/src/routes/auth.d.ts.map +1 -1
  174. package/dist/src/routes/auth.js +77 -4
  175. package/dist/src/routes/auth.js.map +1 -1
  176. package/dist/src/routes/cortex.d.ts +2 -1
  177. package/dist/src/routes/cortex.d.ts.map +1 -1
  178. package/dist/src/routes/cortex.js +13 -8
  179. package/dist/src/routes/cortex.js.map +1 -1
  180. package/dist/src/routes/extensions.d.ts.map +1 -1
  181. package/dist/src/routes/extensions.js +85 -51
  182. package/dist/src/routes/extensions.js.map +1 -1
  183. package/dist/src/routes/generator.d.ts.map +1 -1
  184. package/dist/src/routes/generator.js +109 -43
  185. package/dist/src/routes/generator.js.map +1 -1
  186. package/dist/src/routes/ghii.d.ts.map +1 -1
  187. package/dist/src/routes/ghii.js +34 -13
  188. package/dist/src/routes/ghii.js.map +1 -1
  189. package/dist/src/routes/libs.d.ts +15 -0
  190. package/dist/src/routes/libs.d.ts.map +1 -1
  191. package/dist/src/routes/libs.js +178 -55
  192. package/dist/src/routes/libs.js.map +1 -1
  193. package/dist/src/routes/portal.d.ts.map +1 -1
  194. package/dist/src/routes/portal.js +1 -0
  195. package/dist/src/routes/portal.js.map +1 -1
  196. package/dist/src/routes/schedules.d.ts +33 -0
  197. package/dist/src/routes/schedules.d.ts.map +1 -0
  198. package/dist/src/routes/schedules.js +380 -0
  199. package/dist/src/routes/schedules.js.map +1 -0
  200. package/dist/src/server-bootstrap/config-init.js +2 -2
  201. package/dist/src/server-bootstrap/config-init.js.map +1 -1
  202. package/dist/src/server-bootstrap/routes-loader.d.ts.map +1 -1
  203. package/dist/src/server-bootstrap/routes-loader.js +7 -0
  204. package/dist/src/server-bootstrap/routes-loader.js.map +1 -1
  205. package/dist/src/server-bootstrap/service-init.d.ts.map +1 -1
  206. package/dist/src/server-bootstrap/service-init.js +2 -1
  207. package/dist/src/server-bootstrap/service-init.js.map +1 -1
  208. package/dist/src/services/access-token.d.ts +28 -0
  209. package/dist/src/services/access-token.d.ts.map +1 -0
  210. package/dist/src/services/access-token.js +37 -0
  211. package/dist/src/services/access-token.js.map +1 -0
  212. package/dist/src/services/ai-completion.d.ts +83 -0
  213. package/dist/src/services/ai-completion.d.ts.map +1 -0
  214. package/dist/src/services/ai-completion.js +187 -0
  215. package/dist/src/services/ai-completion.js.map +1 -0
  216. package/dist/src/services/config-schema.d.ts.map +1 -1
  217. package/dist/src/services/config-schema.js +3 -0
  218. package/dist/src/services/config-schema.js.map +1 -1
  219. package/dist/src/services/generator-prompt-seeds.d.ts.map +1 -1
  220. package/dist/src/services/generator-prompt-seeds.js +6 -3
  221. package/dist/src/services/generator-prompt-seeds.js.map +1 -1
  222. package/dist/src/services/generator-prompts/spec-validate.d.ts.map +1 -1
  223. package/dist/src/services/generator-prompts/spec-validate.js +6 -2
  224. package/dist/src/services/generator-prompts/spec-validate.js.map +1 -1
  225. package/dist/src/services/generator-registration.d.ts +12 -4
  226. package/dist/src/services/generator-registration.d.ts.map +1 -1
  227. package/dist/src/services/generator-registration.js +71 -0
  228. package/dist/src/services/generator-registration.js.map +1 -1
  229. package/dist/src/services/generator-validate.d.ts +10 -0
  230. package/dist/src/services/generator-validate.d.ts.map +1 -1
  231. package/dist/src/services/generator-validate.js +180 -36
  232. package/dist/src/services/generator-validate.js.map +1 -1
  233. package/dist/src/services/owner-session.d.ts +43 -0
  234. package/dist/src/services/owner-session.d.ts.map +1 -0
  235. package/dist/src/services/owner-session.js +175 -0
  236. package/dist/src/services/owner-session.js.map +1 -0
  237. package/dist/src/services/schedule-constraints.d.ts +65 -0
  238. package/dist/src/services/schedule-constraints.d.ts.map +1 -0
  239. package/dist/src/services/schedule-constraints.js +106 -0
  240. package/dist/src/services/schedule-constraints.js.map +1 -0
  241. package/dist/src/services/scheduler.d.ts +34 -0
  242. package/dist/src/services/scheduler.d.ts.map +1 -1
  243. package/dist/src/services/scheduler.js +266 -22
  244. package/dist/src/services/scheduler.js.map +1 -1
  245. package/dist/src/services/skill-bundle/generator.js +19 -0
  246. package/dist/src/services/skill-bundle/generator.js.map +1 -1
  247. package/dist/src/storage/interface.d.ts +48 -4
  248. package/dist/src/storage/interface.d.ts.map +1 -1
  249. package/dist/src/storage/interface.js.map +1 -1
  250. package/dist/src/storage/providers/mongodb/index.d.ts +31 -0
  251. package/dist/src/storage/providers/mongodb/index.d.ts.map +1 -1
  252. package/dist/src/storage/providers/mongodb/index.js +187 -9
  253. package/dist/src/storage/providers/mongodb/index.js.map +1 -1
  254. package/dist/src/storage/providers/sqlite/index.d.ts +31 -0
  255. package/dist/src/storage/providers/sqlite/index.d.ts.map +1 -1
  256. package/dist/src/storage/providers/sqlite/index.js +131 -8
  257. package/dist/src/storage/providers/sqlite/index.js.map +1 -1
  258. package/dist/src/storage/providers/sqlite/repos/agent-task.d.ts.map +1 -1
  259. package/dist/src/storage/providers/sqlite/repos/agent-task.js +6 -4
  260. package/dist/src/storage/providers/sqlite/repos/agent-task.js.map +1 -1
  261. package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
  262. package/dist/src/storage/providers/sqlite/schema.js +79 -8
  263. package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
  264. package/dist/src/storage/repositories/pat.repository.d.ts +40 -0
  265. package/dist/src/storage/repositories/pat.repository.d.ts.map +1 -0
  266. package/dist/src/storage/repositories/pat.repository.js +14 -0
  267. package/dist/src/storage/repositories/pat.repository.js.map +1 -0
  268. package/dist/src/storage/repositories/scheduler.repository.d.ts +2 -0
  269. package/dist/src/storage/repositories/scheduler.repository.d.ts.map +1 -1
  270. package/dist/src/storage/repositories/session.repository.d.ts +51 -0
  271. package/dist/src/storage/repositories/session.repository.d.ts.map +1 -1
  272. package/dist/src/storage/repositories/session.repository.js +12 -0
  273. package/dist/src/storage/repositories/session.repository.js.map +1 -1
  274. package/dist/src/utils/env-config.d.ts.map +1 -1
  275. package/dist/src/utils/env-config.js +25 -1
  276. package/dist/src/utils/env-config.js.map +1 -1
  277. package/dist/src/utils/env-validator.d.ts.map +1 -1
  278. package/dist/src/utils/env-validator.js +4 -0
  279. package/dist/src/utils/env-validator.js.map +1 -1
  280. package/package.json +1 -1
  281. package/prisma/schema.prisma +48 -0
@@ -9,6 +9,19 @@
9
9
  * - TaskItem -- task row with expand/collapse, todo list, start button
10
10
  * - RequestChangesModal -- inline modal for owner to send a free-text change request
11
11
  * @version-history
12
+ * v4.8.3 -- 2026-06-02 -- Render non-JSON memory values (e.g. an agent's
13
+ * latest_output) as Markdown via the shared safe Markdown component, instead
14
+ * of raw monospace text.
15
+ * v4.8.2 -- 2026-06-01 -- Render JSON memory values as a structured key/value
16
+ * tree (indented nested blocks, type-coloured values) instead of raw JSON text.
17
+ * v4.8.1 -- 2026-06-01 -- Per-task memory entries are now collapsible; values
18
+ * render as pretty-printed JSON when valid (raw otherwise). Add deliverableKey
19
+ * as a third lookup source alongside the task:<id> tag and live-key prefix.
20
+ * v4.8.0 -- 2026-06-01 -- Show a task's memory entries in the expanded view:
21
+ * entries tagged task:<id> + the live-status key prefix, deduped by key.
22
+ * v4.7.0 -- 2026-06-01 -- Triage: replace status pills with Recent/Keep/Archive
23
+ * buckets (server-derived, with counts), on-demand search (🔍 toggle) + time
24
+ * chips, and per-task Keep/Archive/Restore actions. Fetches by bucket/q/time.
12
25
  * v4.6.0 -- 2026-06-01 -- Add "max concurrent tasks" runner config (number input,
13
26
  * default 1) that PATCHes /v1/agents/:name/max-concurrent-tasks. Consumed by
14
27
  * the agent's runner (e.g. a CrewAI daemon) via the integration kit.
@@ -49,12 +62,24 @@ const html = htm.bind(h);
49
62
  import { t } from '/js/i18n.js';
50
63
  import { timeAgo } from '/js/utils.js';
51
64
  import { apiGet, apiPost } from '/js/api.js';
52
- import { listTasks, deleteTask, startTask, listEvents, requestChanges, createTask, rateTask } from '/js/services/agent-tasks.js';
65
+ import { listTasks, deleteTask, startTask, listEvents, requestChanges, createTask, rateTask, setTaskTriage } from '/js/services/agent-tasks.js';
53
66
  import { setMaxConcurrentTasks } from '/js/services/agents.js';
54
67
  import { useConfirm, Modal } from '/components/Modal.js';
68
+ import { Markdown } from '/components/Markdown.js';
55
69
  import RateModal from './agents/rate-modal.js';
56
70
 
57
- const TASK_FILTERS = ['all', 'active', 'queued', 'completed', 'failed'];
71
+ // Tasks-tab triage buckets (server-derived) + on-demand search time chips.
72
+ const BUCKETS = ['recent', 'keep', 'archive'];
73
+ const TIME_CHIPS = ['all', 'today', '7d', '30d'];
74
+
75
+ /** Map a time chip to an `updated_after` ISO bound (undefined = no bound). */
76
+ function timeChipToAfter(chip) {
77
+ const now = new Date();
78
+ if (chip === 'today') { const d = new Date(now); d.setHours(0, 0, 0, 0); return d.toISOString(); }
79
+ if (chip === '7d') return new Date(now.getTime() - 7 * 86400000).toISOString();
80
+ if (chip === '30d') return new Date(now.getTime() - 30 * 86400000).toISOString();
81
+ return undefined;
82
+ }
58
83
 
59
84
  // Per-browser "blur the title" preference. Used when screen-recording the tab
60
85
  // so sensitive task titles can be hidden without affecting other viewers or
@@ -212,6 +237,74 @@ function RequestChangesModal({ open, onClose, onSubmit, submitting }) {
212
237
  <//>`;
213
238
  }
214
239
 
240
+ // Parse a memory value into structured JSON when possible. Returns { json } for
241
+ // objects (or strings that parse as JSON), or { raw } for plain text/markdown.
242
+ function parseMemoryValue(value) {
243
+ if (value === null || value === undefined) return { raw: '' };
244
+ if (typeof value === 'object') return { json: value };
245
+ if (typeof value === 'string') {
246
+ const trimmed = value.trim();
247
+ if (trimmed && (trimmed[0] === '{' || trimmed[0] === '[')) {
248
+ try { return { json: JSON.parse(trimmed) }; }
249
+ catch { /* not JSON after all -- show raw */ }
250
+ }
251
+ return { raw: value };
252
+ }
253
+ return { raw: String(value) };
254
+ }
255
+
256
+ // Recursive structured JSON renderer: objects/arrays become indented key/value
257
+ // rows, primitives get type-coloured values. Far easier to scan than raw JSON.
258
+ function JsonNode({ value }) {
259
+ if (value === null) return html`<span class="pf-agd-json-null">null</span>`;
260
+ const t = typeof value;
261
+ if (t === 'string') return html`<span class="pf-agd-json-str">${value}</span>`;
262
+ if (t === 'number') return html`<span class="pf-agd-json-num">${value}</span>`;
263
+ if (t === 'boolean') return html`<span class="pf-agd-json-bool">${value ? 'true' : 'false'}</span>`;
264
+ const entries = Array.isArray(value) ? value.map((v, i) => [String(i), v]) : Object.entries(value || {});
265
+ if (entries.length === 0) return html`<span class="pf-agd-json-empty">${Array.isArray(value) ? '[ ]' : '{ }'}</span>`;
266
+ return html`
267
+ <div class="pf-agd-json-block">
268
+ ${entries.map(([k, v]) => {
269
+ const nested = v !== null && typeof v === 'object';
270
+ return html`
271
+ <div class=${`pf-agd-json-row ${nested ? 'pf-agd-json-row--nested' : ''}`} key=${k}>
272
+ <span class="pf-agd-json-key">${k}</span>
273
+ <${JsonNode} value=${v} />
274
+ </div>
275
+ `;
276
+ })}
277
+ </div>
278
+ `;
279
+ }
280
+
281
+ // One collapsible memory entry. Header (key + JSON badge) toggles the body, which
282
+ // renders a structured JSON view when the value is JSON, raw text otherwise.
283
+ function TaskMemoryEntry({ entry }) {
284
+ const [open, setOpen] = useState(false);
285
+ const { json, raw } = parseMemoryValue(entry.value);
286
+ const isJson = json !== undefined;
287
+ return html`
288
+ <div class="pf-agd-task-memory-entry">
289
+ <button class="pf-agd-task-memory-head" onClick=${(e) => { e.stopPropagation(); setOpen(o => !o); }} aria-expanded=${open}>
290
+ <span class="pf-agd-task-memory-caret">${open ? '▼' : '▶'}</span>
291
+ <code class="pf-agd-task-memory-key">${entry.key}</code>
292
+ ${isJson && html`<span class="pf-agd-task-memory-badge">JSON</span>`}
293
+ </button>
294
+ ${open && html`
295
+ <div class="pf-agd-task-memory-body">
296
+ ${isJson
297
+ ? html`<${JsonNode} value=${json} />`
298
+ // Non-JSON values (e.g. an agent's latest_output) are usually
299
+ // markdown — render them formatted via the shared safe Markdown
300
+ // component instead of raw text.
301
+ : html`<div class="pf-agd-task-memory-md"><${Markdown} text=${raw} /></div>`}
302
+ </div>
303
+ `}
304
+ </div>
305
+ `;
306
+ }
307
+
215
308
  function TaskItem({ task, agentName, showToast, onRefresh }) {
216
309
  const [expanded, setExpanded] = useState(false);
217
310
  const [events, setEvents] = useState(null);
@@ -224,6 +317,8 @@ function TaskItem({ task, agentName, showToast, onRefresh }) {
224
317
  const [showOutdated, setShowOutdated] = useState(false);
225
318
  // Deliverable preview: null = not requested, {loading}|{notFound}|{value}.
226
319
  const [deliverable, setDeliverable] = useState(null);
320
+ // This task's memory entries: null = not requested, {loading}|{items}.
321
+ const [taskMemory, setTaskMemory] = useState(null);
227
322
  // Local-only "hide the title" toggle (for screen recordings). Persisted per
228
323
  // task ID in localStorage; see helpers at top of file.
229
324
  const [blurred, setBlurred] = useState(() => isTaskBlurred(task.id));
@@ -256,6 +351,43 @@ function TaskItem({ task, agentName, showToast, onRefresh }) {
256
351
  }
257
352
  }
258
353
 
354
+ // Fetch the memory entries that belong to this task. The canonical handle is
355
+ // the `task:<full-id>` tag (the runner tags every write: deliverable, live,
356
+ // delegated deliverable + evalctx) -- the deliverable key embeds a SHORT id so
357
+ // it can't be matched by key substring, which is exactly why the tag exists.
358
+ // Two extra fallbacks make it robust for tasks written before the tag landed:
359
+ // the live-status key prefix (full id) and the task's own recorded
360
+ // deliverableKey. Rolling/public stats (statistics.custom.*) are NOT per-task
361
+ // tagged, so they never appear here. Deduped by key.
362
+ async function fetchTaskMemory() {
363
+ const gaii = task.agentGaii;
364
+ if (!gaii) { setTaskMemory({ items: [] }); return; }
365
+ setTaskMemory({ loading: true });
366
+ try {
367
+ const tag = `task:${task.id}`;
368
+ const livePrefix = `agents.${agentName}.tasks.${task.id}.`;
369
+ const fetches = [
370
+ apiGet(`/v1/memory?agent=${encodeURIComponent(gaii)}&tags=${encodeURIComponent(tag)}&per_page=50`).catch(() => null),
371
+ apiGet(`/v1/memory?agent=${encodeURIComponent(gaii)}&prefix=${encodeURIComponent(livePrefix)}&per_page=20`).catch(() => null),
372
+ ];
373
+ if (task.deliverableKey) {
374
+ fetches.push(apiGet(`/v1/memory?agent=${encodeURIComponent(gaii)}&prefix=${encodeURIComponent(task.deliverableKey)}&per_page=5`).catch(() => null));
375
+ }
376
+ const results = await Promise.all(fetches);
377
+ const items = [];
378
+ const seen = new Set();
379
+ for (const resp of results) {
380
+ const list = resp?.data?.items || resp?.data || [];
381
+ for (const it of (Array.isArray(list) ? list : [])) {
382
+ if (it.key && !seen.has(it.key)) { seen.add(it.key); items.push(it); }
383
+ }
384
+ }
385
+ setTaskMemory({ items });
386
+ } catch {
387
+ setTaskMemory({ items: [] });
388
+ }
389
+ }
390
+
259
391
  async function fetchEvents({ silent = false } = {}) {
260
392
  if (!silent) setLoadingEvents(true);
261
393
  try {
@@ -352,6 +484,19 @@ function TaskItem({ task, agentName, showToast, onRefresh }) {
352
484
  setShowRateModal(true);
353
485
  }
354
486
 
487
+ async function handleTriage(e, triage) {
488
+ e.stopPropagation();
489
+ try {
490
+ await setTaskTriage(agentName, task.id, triage);
491
+ showToast(t(triage === 'kept' ? 'profile.agents.tasks.triage.kept'
492
+ : triage === 'archived' ? 'profile.agents.tasks.triage.archived'
493
+ : 'profile.agents.tasks.triage.restored'));
494
+ onRefresh();
495
+ } catch (err) {
496
+ showToast(err.message || t('profile.agents.tasks.triage.error'), true);
497
+ }
498
+ }
499
+
355
500
  async function handleSubmitRate(body) {
356
501
  setSendingRate(true);
357
502
  try {
@@ -441,6 +586,21 @@ function TaskItem({ task, agentName, showToast, onRefresh }) {
441
586
  `}
442
587
  `}
443
588
 
589
+ <div class="pf-agd-task-memory">
590
+ <button class="btn-ghost btn-sm" onClick=${(e) => { e.stopPropagation(); fetchTaskMemory(); }}>
591
+ ${t('profile.agents.tasks.memory.show')}
592
+ </button>
593
+ ${taskMemory && (taskMemory.loading
594
+ ? html`<div class="pf-agd-empty">${t('profile.loading')}</div>`
595
+ : taskMemory.items.length === 0
596
+ ? html`<div class="pf-agd-empty">${t('profile.agents.tasks.memory.none')}</div>`
597
+ : html`
598
+ <div class="pf-agd-task-memory-list">
599
+ ${taskMemory.items.map(it => html`<${TaskMemoryEntry} key=${it.key} entry=${it} />`)}
600
+ </div>
601
+ `)}
602
+ </div>
603
+
444
604
  ${isDone && html`
445
605
  <div class="pf-agd-rate-row">
446
606
  ${rating
@@ -578,6 +738,16 @@ function TaskItem({ task, agentName, showToast, onRefresh }) {
578
738
  ${(isQueued || task.status === 'draft' || isRevisionRequested) && html`
579
739
  <button class="btn-danger btn-sm" onClick=${handleDelete}>${t('profile.agents.tasks.delete')}</button>
580
740
  `}
741
+ <span class="pf-agd-task-actions-spacer"></span>
742
+ ${task.triage !== 'kept' && html`
743
+ <button class="btn-ghost btn-sm" onClick=${(e) => handleTriage(e, 'kept')} title=${t('profile.agents.tasks.triage.keepHint')}>★ ${t('profile.agents.tasks.triage.keep')}</button>
744
+ `}
745
+ ${task.triage !== 'archived' && html`
746
+ <button class="btn-ghost btn-sm" onClick=${(e) => handleTriage(e, 'archived')} title=${t('profile.agents.tasks.triage.archiveHint')}>${t('profile.agents.tasks.triage.archive')}</button>
747
+ `}
748
+ ${task.triage && html`
749
+ <button class="btn-ghost btn-sm" onClick=${(e) => handleTriage(e, null)}>${t('profile.agents.tasks.triage.restore')}</button>
750
+ `}
581
751
  </div>
582
752
  <${ConfirmUI} />
583
753
  <${RequestChangesModal}
@@ -601,9 +771,13 @@ function TaskItem({ task, agentName, showToast, onRefresh }) {
601
771
 
602
772
  export default function AgentTasksSubtab({ agent, agentName, showToast }) {
603
773
  const [tasks, setTasks] = useState(null);
774
+ const [counts, setCounts] = useState({ recent: 0, keep: 0, archive: 0 });
775
+ const [bucket, setBucket] = useState('recent');
604
776
  const [showCreate, setShowCreate] = useState(false);
605
777
  const [error, setError] = useState(null);
606
- const [filter, setFilter] = useState('all');
778
+ const [searchOpen, setSearchOpen] = useState(false);
779
+ const [q, setQ] = useState('');
780
+ const [timeChip, setTimeChip] = useState('all');
607
781
  // Runner concurrency config (default 1 = serial). Saved to the agent via PATCH.
608
782
  const [maxConcurrent, setMaxConcurrent] = useState(agent?.max_concurrent_tasks ?? 1);
609
783
  const [savingConcurrency, setSavingConcurrency] = useState(false);
@@ -625,8 +799,14 @@ export default function AgentTasksSubtab({ agent, agentName, showToast }) {
625
799
 
626
800
  async function loadTasks() {
627
801
  try {
628
- const resp = await listTasks(agentName);
802
+ const resp = await listTasks(agentName, {
803
+ bucket,
804
+ q: q.trim() || undefined,
805
+ updated_after: timeChipToAfter(timeChip),
806
+ per_page: 100,
807
+ });
629
808
  setTasks(resp?.data?.tasks || []);
809
+ setCounts(resp?.data?.counts || { recent: 0, keep: 0, archive: 0 });
630
810
  setError(null);
631
811
  } catch (err) {
632
812
  setError(err.message);
@@ -634,7 +814,11 @@ export default function AgentTasksSubtab({ agent, agentName, showToast }) {
634
814
  }
635
815
  }
636
816
 
637
- useEffect(() => { loadTasks(); }, [agentName]);
817
+ // Refetch on agent/bucket/time change immediately; debounce text search.
818
+ useEffect(() => {
819
+ const id = setTimeout(() => loadTasks(), q ? 300 : 0);
820
+ return () => clearTimeout(id);
821
+ }, [agentName, bucket, timeChip, q]);
638
822
 
639
823
  const loadRef = useRef(loadTasks);
640
824
  loadRef.current = loadTasks;
@@ -653,24 +837,13 @@ export default function AgentTasksSubtab({ agent, agentName, showToast }) {
653
837
  return html`<div class="pf-agd-empty">${t('profile.loading')}</div>`;
654
838
  }
655
839
 
656
- const filtered = filter === 'all' ? tasks : tasks.filter(task => {
657
- switch (filter) {
658
- case 'active': return task.status === 'active';
659
- // 'revision_requested' lives under the queued filter -- the task is
660
- // waiting for the agent to re-propose, which is conceptually the same
661
- // "awaiting action before run" bucket the owner already monitors here.
662
- case 'queued': return task.status === 'queued' || task.status === 'draft' || task.status === 'revision_requested';
663
- case 'completed': return task.status === 'done';
664
- case 'failed': return task.status === 'failed' || task.status === 'stalled';
665
- default: return true;
666
- }
667
- });
840
+ const totalTasks = counts.recent + counts.keep + counts.archive;
668
841
 
669
842
  return html`
670
843
  <div>
671
844
  <div class="pf-agd-section-header">
672
845
  <span class="pf-agd-section-title">
673
- ${t('profile.agents.tasks.title')}${tasks.length > 0 ? ` (${tasks.length})` : ''}
846
+ ${t('profile.agents.tasks.title')}${totalTasks > 0 ? ` (${totalTasks})` : ''}
674
847
  </span>
675
848
  <button class="btn-outline btn-sm" onClick=${() => setShowCreate(!showCreate)}>
676
849
  ${showCreate ? '-' : '+'} ${t('profile.agents.tasks.newTask')}
@@ -700,20 +873,42 @@ export default function AgentTasksSubtab({ agent, agentName, showToast }) {
700
873
 
701
874
  ${error && html`<div class="pf-agd-empty">${error}</div>`}
702
875
 
703
- <div class="pf-agd-filter-bar">
704
- ${TASK_FILTERS.map(f => html`
705
- <button key=${f}
706
- class="pf-agd-filter-pill ${filter === f ? 'pf-agd-filter-pill--active' : ''}"
707
- onClick=${() => setFilter(f)}>
708
- ${t(`profile.agents.detail.tasks.filter.${f}`)}
876
+ <div class="pf-agd-bucket-bar">
877
+ ${BUCKETS.map(b => html`
878
+ <button key=${b}
879
+ class="pf-agd-bucket ${bucket === b ? 'pf-agd-bucket--active' : ''}"
880
+ onClick=${() => setBucket(b)}>
881
+ ${t(`profile.agents.tasks.bucket.${b}`)} <span class="pf-agd-bucket-count">${counts[b] ?? 0}</span>
709
882
  </button>
710
883
  `)}
884
+ <span class="pf-agd-bucket-spacer"></span>
885
+ <button class="pf-agd-search-toggle ${searchOpen ? 'pf-agd-search-toggle--on' : ''}"
886
+ onClick=${() => setSearchOpen(o => !o)}
887
+ title=${t('profile.agents.tasks.search.toggle')}
888
+ aria-pressed=${searchOpen}>🔍</button>
711
889
  </div>
712
890
 
713
- ${filtered.length > 0 ? filtered.map(task => html`
891
+ ${searchOpen && html`
892
+ <div class="pf-agd-search-bar">
893
+ <input class="pf-agd-search-input" type="search"
894
+ placeholder=${t('profile.agents.tasks.search.placeholder')}
895
+ value=${q} onInput=${e => setQ(e.target.value)} />
896
+ <div class="pf-agd-time-chips">
897
+ ${TIME_CHIPS.map(c => html`
898
+ <button key=${c}
899
+ class="pf-agd-time-chip ${timeChip === c ? 'pf-agd-time-chip--on' : ''}"
900
+ onClick=${() => setTimeChip(c)}>
901
+ ${t(`profile.agents.tasks.search.time.${c}`)}
902
+ </button>
903
+ `)}
904
+ </div>
905
+ </div>
906
+ `}
907
+
908
+ ${tasks.length > 0 ? tasks.map(task => html`
714
909
  <${TaskItem} key=${task.id} task=${task} agentName=${agentName} showToast=${showToast} onRefresh=${loadTasks} />
715
910
  `) : html`
716
- <div class="pf-agd-empty">${t('profile.agents.detail.empty.tasks')}</div>
911
+ <div class="pf-agd-empty">${q ? t('profile.agents.tasks.search.noResults') : t(`profile.agents.tasks.bucket.empty.${bucket}`)}</div>
717
912
  `}
718
913
  </div>
719
914
  `;
@@ -33,6 +33,8 @@ function computeWeightedScore(dims) {
33
33
  import htm from 'htm';
34
34
  const html = htm.bind(h);
35
35
  import { t } from '/js/i18n.js';
36
+ import { CopyButton } from '/components/CopyButton.js';
37
+ import { copyToClipboard } from '/js/utils.js';
36
38
  import { getBatch, updateBatch, createVersion } from '/js/services/calibrator.js';
37
39
 
38
40
 
@@ -527,7 +529,7 @@ Now return the full modified instruction prompt with the fixes incorporated. Rem
527
529
  }).filter(Boolean);
528
530
 
529
531
  const text = `Here is a prompt that needs improvement:\n\n---\n${currentVersion.prompt}\n---\n\nApply these proposed fixes:\n${selectedProposals.map((p, i) => `${i + 1}. ${p}`).join('\n')}\n\nReturn ONLY the modified prompt text.`;
530
- navigator.clipboard.writeText(text).then(() => showToast?.('Copied'));
532
+ copyToClipboard(text).then(() => showToast?.('Copied'));
531
533
  }
532
534
 
533
535
  // ── Paste handlers ──
@@ -599,10 +601,6 @@ Now return the full modified instruction prompt with the fixes incorporated. Rem
599
601
  .then(u => { setDetail(u || { ...detail, status: 'synthesized', step4_synthesis: synth }); onUpdate?.(); });
600
602
  }
601
603
 
602
- function copyToClipboard(text, label) {
603
- navigator.clipboard.writeText(text).then(() => showToast?.(label + ' copied'));
604
- }
605
-
606
604
  // ── Render: Step 1 (Generation) ──
607
605
 
608
606
  function renderStep1() {
@@ -631,9 +629,7 @@ Now return the full modified instruction prompt with the fixes incorporated. Rem
631
629
  ` : ''}
632
630
  <div class="fnd-cal-run-actions">
633
631
  ${m.step1_generation?.output ? html`
634
- <button class="btn-ghost btn-sm" onClick=${() => copyToClipboard(m.step1_generation.output, 'Output')}>
635
- ${t('profile.calibrator.copy')}
636
- </button>
632
+ <${CopyButton} text=${m.step1_generation.output} label=${t('profile.calibrator.copy')} className="btn-sm" onCopied=${() => showToast?.('Output copied')} />
637
633
  ` : ''}
638
634
  <${PasteBack} label=${t('profile.calibrator.pasteOutput')} onSave=${text => handlePasteStep1(i, text)} />
639
635
  </div>
@@ -700,9 +696,7 @@ Now return the full modified instruction prompt with the fixes incorporated. Rem
700
696
  ${a?.rawResponse ? html`<${CollapsiblePre} label=${t('profile.calibrator.viewRawResponse')} text=${a.rawResponse} />` : ''}
701
697
  <div class="fnd-cal-run-actions">
702
698
  ${a?.analysis ? html`
703
- <button class="btn-ghost btn-sm" onClick=${() => copyToClipboard(typeof a.analysis === 'string' ? a.analysis : JSON.stringify(a.analysis, null, 2), 'Analysis')}>
704
- ${t('profile.calibrator.copy')}
705
- </button>
699
+ <${CopyButton} text=${typeof a.analysis === 'string' ? a.analysis : JSON.stringify(a.analysis, null, 2)} label=${t('profile.calibrator.copy')} className="btn-sm" onCopied=${() => showToast?.('Analysis copied')} />
706
700
  ` : ''}
707
701
  <${PasteBack} label=${t('profile.calibrator.pasteAnalysis')} onSave=${text => handlePasteStep2(modelIndex, text)} />
708
702
  </div>
@@ -18,6 +18,7 @@ import htm from 'htm';
18
18
  const html = htm.bind(h);
19
19
  import { t } from '/js/i18n.js';
20
20
  import { apiGet, apiDelete } from '/js/api.js';
21
+ import { CopyButton } from '/components/CopyButton.js';
21
22
  import LlmConfigEditor from '/views/profile/calibrator-llm-editor.js';
22
23
  import CalibrationChart from '/views/profile/calibrator-chart.js';
23
24
  import BatchCard from '/views/profile/calibrator-batch.js';
@@ -308,10 +309,6 @@ function ProjectDetailView({ projectId, onBack, showToast }) {
308
309
  }
309
310
  }
310
311
 
311
- function copyToClipboard(text, label) {
312
- navigator.clipboard.writeText(text).then(() => showToast?.(label + ' copied'));
313
- }
314
-
315
312
  async function handleArchive() {
316
313
  await handleUpdateProject({ status: 'archived' });
317
314
  onBack();
@@ -388,14 +385,14 @@ function ProjectDetailView({ projectId, onBack, showToast }) {
388
385
  <div class="fnd-cal-editor-label">${t('profile.calibrator.prompt')} (v${selectedVersion || 0})</div>
389
386
  <textarea value=${prompt} onInput=${e => setPrompt(e.target.value)} disabled=${isReadOnly} />
390
387
  <div class="fnd-cal-editor-actions">
391
- <button class="btn-ghost btn-sm" onClick=${() => copyToClipboard(prompt, 'Prompt')}>${t('profile.calibrator.copy')}</button>
388
+ <${CopyButton} text=${prompt} label=${t('profile.calibrator.copy')} className="btn-sm" onCopied=${() => showToast?.('Prompt copied')} />
392
389
  </div>
393
390
  </div>
394
391
  <div class="fnd-cal-editor-panel">
395
392
  <div class="fnd-cal-editor-label">${t('profile.calibrator.targetOutput')}</div>
396
393
  <textarea value=${targetOutput} onInput=${e => setTargetOutput(e.target.value)} disabled=${isReadOnly} />
397
394
  <div class="fnd-cal-editor-actions">
398
- <button class="btn-ghost btn-sm" onClick=${() => copyToClipboard(targetOutput, 'Target')}>${t('profile.calibrator.copy')}</button>
395
+ <${CopyButton} text=${targetOutput} label=${t('profile.calibrator.copy')} className="btn-sm" onCopied=${() => showToast?.('Target copied')} />
399
396
  </div>
400
397
  </div>
401
398
  </div>
@@ -440,9 +437,7 @@ function ProjectDetailView({ projectId, onBack, showToast }) {
440
437
  <button class="btn-ghost btn-sm" onClick=${() => handleResetTemplate(tc.field)}>
441
438
  ${t('profile.calibrator.resetTemplate')}
442
439
  </button>
443
- <button class="btn-ghost btn-sm" onClick=${() => copyToClipboard(templates[tc.field], 'Template')}>
444
- ${t('profile.calibrator.copy')}
445
- </button>
440
+ <${CopyButton} text=${templates[tc.field]} label=${t('profile.calibrator.copy')} className="btn-sm" onCopied=${() => showToast?.('Template copied')} />
446
441
  </div>
447
442
  </div>
448
443
  </details>
@@ -6,14 +6,18 @@
6
6
  * @version-history
7
7
  * v1.0.0 — 2026-03-16 — Initial chat sessions tab
8
8
  * v1.1.0 — 2026-03-17 — Replace inline styles with CSS classes; fix fallback strings
9
+ * v1.2.0 — 2026-06-02 — Component unification (#1): "Copy GAII" uses canonical
10
+ * <CopyButton> (toast preserved); prompt-copy routed through shared copyToClipboard
11
+ * (insecure-context fallback) instead of raw navigator.clipboard.
9
12
  */
10
13
  import { h } from 'preact';
11
14
  import { useState, useEffect, useCallback, useRef } from 'preact/hooks';
12
15
  import htm from 'htm';
13
16
  const html = htm.bind(h);
14
17
  import { t } from '/js/i18n.js';
15
- import { escHtml, timeAgo } from '/js/utils.js';
18
+ import { escHtml, timeAgo, copyToClipboard } from '/js/utils.js';
16
19
  import { Spinner } from './shared.js';
20
+ import { CopyButton } from '/components/CopyButton.js';
17
21
  import { useConfirm } from '/components/Modal.js';
18
22
  import { listAgents, deleteAgent } from '/js/services/agents.js';
19
23
  import { apiGet } from '/js/api.js';
@@ -75,7 +79,7 @@ export default function ChatSessionsTab({ session, showToast, onStats }) {
75
79
  const resp = await apiGet(endpoint);
76
80
  const text = resp?.data?.prompt;
77
81
  if (text) {
78
- await navigator.clipboard.writeText(text);
82
+ await copyToClipboard(text);
79
83
  showToast(t('profile.chatSessions.promptCopied'));
80
84
  } else {
81
85
  showToast(t('profile.chatSessions.promptError'));
@@ -165,9 +169,9 @@ export default function ChatSessionsTab({ session, showToast, onStats }) {
165
169
  </div>
166
170
 
167
171
  <div class="card-actions flex-actions">
168
- <button class="btn-outline btn-sm" onClick=${(e) => { e.stopPropagation(); navigator.clipboard.writeText(s.gaii || s.name); showToast('GAII copied'); }}>
169
- Copy GAII
170
- </button>
172
+ <span onClick=${(e) => e.stopPropagation()}>
173
+ <${CopyButton} text=${s.gaii || s.name} label=${t('profile.agents.copyGaii')} className="btn-outline btn-sm" onCopied=${() => showToast('GAII copied')} />
174
+ </span>
171
175
  <button class="btn-danger-solid btn-sm" onClick=${(e) => { e.stopPropagation(); handleDelete(s); }}
172
176
  disabled=${deleting === s.name}>
173
177
  ${deleting === s.name ? '...' : t('profile.chatSessions.remove')}
@@ -130,7 +130,7 @@ export default function EmailTab({ session, showToast }) {
130
130
  <p class="email-sent-text">${t('profile.email.codeSent')}</p>
131
131
  <div class="email-form-group">
132
132
  <label class="email-form-label">${t('profile.email.enterCode')}</label>
133
- <input class="pf-input input-verification-code" type="text" maxlength="6" placeholder="123456"
133
+ <input class="input-field input-verification-code" type="text" maxlength="6" placeholder="123456"
134
134
  value=${code} onInput=${e => setCode(e.target.value)} />
135
135
  </div>
136
136
  <button class="btn-primary" onClick=${handleVerifyCode} disabled=${verifying}>
@@ -147,7 +147,7 @@ export default function EmailTab({ session, showToast }) {
147
147
  ` : null}
148
148
  <div class="email-form-group">
149
149
  <label class="email-form-label">${t('profile.email.enterEmail')}</label>
150
- <input class="pf-input" type="email" placeholder="you@example.com"
150
+ <input class="input-field" type="email" placeholder="you@example.com"
151
151
  value=${email} onInput=${e => setEmail(e.target.value)} />
152
152
  </div>
153
153
  <button class="btn-primary" onClick=${handleSendCode} disabled=${sending}>