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
@@ -7,6 +7,9 @@
7
7
  * v1.0.0 -- 2026-05-24 -- Initial creation for Governance Phase C
8
8
  * v1.1.0 -- 2026-05-24 -- Fix M8 M9 F19 F20 F21 audit findings
9
9
  * v1.2.0 -- 2026-05-24 -- Move readiness into onboarding, add bundle templates, add notify button
10
+ * v1.3.0 -- 2026-06-02 -- Admin design unification: main btn-* classes → adm-btn /
11
+ * adm-btn-action; the bespoke adm-agi-stat-card stat rows → canonical <StatsGrid>
12
+ * (tones success→green, accent→indigo). adm-agi-mono-sm retained (table cells).
10
13
  */
11
14
  import { h } from 'preact';
12
15
  import { useState, useEffect, useCallback } from 'preact/hooks';
@@ -14,7 +17,7 @@ import htm from 'htm';
14
17
  const html = htm.bind(h);
15
18
  import { t } from '/js/i18n.js';
16
19
  import { useViewCSS } from '/components/useViewCSS.js';
17
- import { num, dt, Empty } from './shared.js';
20
+ import { num, dt, Empty, StatsGrid } from './shared.js';
18
21
  import * as api from '/js/services/admin-agent-integration.js';
19
22
 
20
23
 
@@ -113,7 +116,7 @@ function renderPlatformRegistry(platforms, totalAgents, session, loadData) {
113
116
  }
114
117
  ${showForm
115
118
  ? html`<${AddPlatformForm} session=${session} onDone=${() => { setShowForm(false); loadData(); }} onCancel=${() => setShowForm(false)} />`
116
- : html`<button class="btn-outline adm-agi-add-btn" onClick=${() => setShowForm(true)}>
119
+ : html`<button class="adm-btn-action adm-agi-add-btn" onClick=${() => setShowForm(true)}>
117
120
  ${t('admin.agentIntegration.addPlatform')}
118
121
  </button>`
119
122
  }
@@ -163,10 +166,10 @@ function AddPlatformForm({ session, onDone, onCancel }) {
163
166
  <input class="adm-agi-add-form-input" value=${pattern} onInput=${e => setPattern(e.target.value)} placeholder=${t('admin.agentIntegration.placeholders.regex')} />
164
167
  </div>
165
168
  <div class="adm-agi-add-form-actions">
166
- <button class="btn-primary" onClick=${handleSubmit} disabled=${submitting || !name.trim() || !id.trim()}>
169
+ <button class="adm-btn" onClick=${handleSubmit} disabled=${submitting || !name.trim() || !id.trim()}>
167
170
  ${submitting ? t('common.loading') : t('common.add')}
168
171
  </button>
169
- <button class="btn-ghost" onClick=${onCancel}>${t('common.cancel')}</button>
172
+ <button class="adm-btn-action" onClick=${onCancel}>${t('common.cancel')}</button>
170
173
  </div>
171
174
  </div>
172
175
  `;
@@ -183,20 +186,11 @@ function renderOnboardingOverview(onboarding, readiness, session, loadData) {
183
186
  return html`
184
187
  <div class="adm-agi-section">
185
188
  <div class="adm-agi-section-title">${t('admin.agentIntegration.onboardingOverview')}</div>
186
- <div class="adm-agi-stats">
187
- <div class="adm-agi-stat-card">
188
- <div class="adm-agi-stat-value adm-agi-stat-value--success">${num(onboarding.completed)}</div>
189
- <div class="adm-agi-stat-label">${t('admin.agentIntegration.completed')}</div>
190
- </div>
191
- <div class="adm-agi-stat-card">
192
- <div class="adm-agi-stat-value adm-agi-stat-value--accent">${num(onboarding.in_progress)}</div>
193
- <div class="adm-agi-stat-label">${t('admin.agentIntegration.inProgress')}</div>
194
- </div>
195
- <div class="adm-agi-stat-card">
196
- <div class="adm-agi-stat-value">${num(notStarted)}</div>
197
- <div class="adm-agi-stat-label">${t('admin.agentIntegration.notStarted')}</div>
198
- </div>
199
- </div>
189
+ <${StatsGrid} items=${[
190
+ { label: t('admin.agentIntegration.completed'), value: onboarding.completed, tone: 'green' },
191
+ { label: t('admin.agentIntegration.inProgress'), value: onboarding.in_progress, tone: 'indigo' },
192
+ { label: t('admin.agentIntegration.notStarted'), value: notStarted },
193
+ ]} />
200
194
  ${stuck.length > 0 ? html`
201
195
  <div class="adm-agi-stuck-title">${t('admin.agentIntegration.stuckAgents')}</div>
202
196
  <div class="adm-agi-stuck">
@@ -246,10 +240,10 @@ function StuckAgentRow({ agent, session, onAction }) {
246
240
  <div class="adm-agi-stuck-suggestion">${suggestion}</div>
247
241
  </div>
248
242
  <div class="adm-agi-stuck-actions">
249
- <button class="btn-outline adm-agi-stuck-btn" onClick=${handleRemind} disabled=${acting}>
243
+ <button class="adm-btn-action adm-agi-stuck-btn" onClick=${handleRemind} disabled=${acting}>
250
244
  ${t('admin.agentIntegration.sendReminder')}
251
245
  </button>
252
- <button class="btn-ghost adm-agi-stuck-btn" onClick=${handleSkip} disabled=${acting}>
246
+ <button class="adm-btn-action adm-agi-stuck-btn" onClick=${handleSkip} disabled=${acting}>
253
247
  ${t('admin.agentIntegration.skipStep')}
254
248
  </button>
255
249
  </div>
@@ -337,7 +331,7 @@ function renderSkillBundles(bundles, onRegenerate, regenerating, session) {
337
331
  : html`<span class="adm-agi-zero">0</span>`
338
332
  }</td>
339
333
  <td>${info.outdated > 0 ? html`
340
- <button class="btn-outline adm-agi-notify-btn" onClick=${() => handleNotify(platform)}>
334
+ <button class="adm-btn-action adm-agi-notify-btn" onClick=${() => handleNotify(platform)}>
341
335
  ${t('admin.agentIntegration.notifyOutdated')}
342
336
  </button>
343
337
  ` : ''}</td>
@@ -347,7 +341,7 @@ function renderSkillBundles(bundles, onRegenerate, regenerating, session) {
347
341
  </table>
348
342
  `
349
343
  }
350
- <button class="btn-outline adm-agi-regen-btn" onClick=${onRegenerate} disabled=${regenerating}>
344
+ <button class="adm-btn-action adm-agi-regen-btn" onClick=${onRegenerate} disabled=${regenerating}>
351
345
  ${regenerating ? t('common.loading') : t('admin.agentIntegration.regenerateAll')}
352
346
  </button>
353
347
  </div>
@@ -363,25 +357,16 @@ function renderBundleTemplates() {
363
357
  return html`
364
358
  <div class="adm-agi-section">
365
359
  <div class="adm-agi-section-title">${t('admin.agentIntegration.bundleTemplates')}</div>
366
- <div class="adm-agi-stats">
367
- <div class="adm-agi-stat-card">
368
- <div class="adm-agi-stat-value">0</div>
369
- <div class="adm-agi-stat-label">${t('admin.agentIntegration.customReferences')}</div>
370
- </div>
371
- <div class="adm-agi-stat-card">
372
- <div class="adm-agi-stat-value">0</div>
373
- <div class="adm-agi-stat-label">${t('admin.agentIntegration.customScripts')}</div>
374
- </div>
375
- <div class="adm-agi-stat-card">
376
- <div class="adm-agi-stat-value adm-agi-mono-sm">--</div>
377
- <div class="adm-agi-stat-label">${t('admin.agentIntegration.lastGenerated')}</div>
378
- </div>
379
- </div>
360
+ <${StatsGrid} items=${[
361
+ { label: t('admin.agentIntegration.customReferences'), value: 0 },
362
+ { label: t('admin.agentIntegration.customScripts'), value: 0 },
363
+ { label: t('admin.agentIntegration.lastGenerated'), value: '--' },
364
+ ]} />
380
365
  <div class="adm-agi-btn-row">
381
- <button class="btn-outline" onClick=${handleComingSoon}>
366
+ <button class="adm-btn-action" onClick=${handleComingSoon}>
382
367
  ${t('admin.agentIntegration.customizeBundle')}
383
368
  </button>
384
- <button class="btn-outline" onClick=${handleComingSoon}>
369
+ <button class="adm-btn-action" onClick=${handleComingSoon}>
385
370
  ${t('admin.agentIntegration.previewBundle')}
386
371
  </button>
387
372
  </div>
@@ -3,6 +3,9 @@
3
3
  * @description Admin dashboard tab for browsing all agent tasks across all owners.
4
4
  * Provides status filtering, pagination, and TODO progress display.
5
5
  * @version-history
6
+ * v1.1.0 -- 2026-06-02 -- Component unification (#16): replace inline hex status
7
+ * colors with tokenized .tag--status-* classes (admin.css) so status tags flip
8
+ * correctly in dark mode.
6
9
  * v1.0.0 -- 2026-05-21 -- Initial creation for Agent Dashboard Phase 1
7
10
  */
8
11
  import { h } from 'preact';
@@ -11,7 +14,7 @@ import htm from 'htm';
11
14
  const html = htm.bind(h);
12
15
  import { t } from '/js/i18n.js';
13
16
  import { escHtml } from '/js/utils.js';
14
- import { num, dt, Empty, Badge } from './shared.js';
17
+ import { dt, Empty, Badge, StatsGrid } from './shared.js';
15
18
  import { apiGet } from '/js/api.js';
16
19
 
17
20
  const PAGE_SIZE = 20;
@@ -25,8 +28,8 @@ export default function AgentTasksTab({ data, reload, session }) {
25
28
  const [loading, setLoading] = useState(false);
26
29
  const [statusFilter, setStatusFilter] = useState('');
27
30
 
28
- const loadTasks = useCallback(async (p = 1) => {
29
- setLoading(true);
31
+ const loadTasks = useCallback(async (p = 1, { showSpinner = true } = {}) => {
32
+ if (showSpinner) setLoading(true);
30
33
  try {
31
34
  const params = new URLSearchParams();
32
35
  params.set('page', String(p));
@@ -48,7 +51,7 @@ export default function AgentTasksTab({ data, reload, session }) {
48
51
 
49
52
  // Listen for live updates
50
53
  useEffect(() => {
51
- const handler = () => { loadTasks(page); };
54
+ const handler = () => { loadTasks(page, { showSpinner: false }); }; // silent: no flash
52
55
  window.addEventListener('aimeat-live-update', handler);
53
56
  return () => window.removeEventListener('aimeat-live-update', handler);
54
57
  }, [loadTasks, page]);
@@ -57,16 +60,11 @@ export default function AgentTasksTab({ data, reload, session }) {
57
60
  setStatusFilter(e.target.value);
58
61
  }
59
62
 
60
- function statusColor(status) {
61
- switch (status) {
62
- case 'active': return 'var(--accent, #06b6d4)';
63
- case 'done': return '#22c55e';
64
- case 'failed': return '#ef4444';
65
- case 'stalled': return '#eab308';
66
- case 'queued': return '#a855f7';
67
- case 'draft': return 'var(--text-dim)';
68
- default: return 'var(--text-dim)';
69
- }
63
+ // Map a task status to a tokenized status-tag class (colors live in admin.css,
64
+ // tag--status-* — replaces the former inline hex colors so chips flip in dark mode).
65
+ function statusTagClass(status) {
66
+ const known = ['active', 'done', 'failed', 'stalled', 'queued', 'draft'];
67
+ return known.includes(status) ? `tag tag--status-${status}` : 'tag tag--status-draft';
70
68
  }
71
69
 
72
70
  const totalPages = Math.ceil(total / PAGE_SIZE);
@@ -87,12 +85,7 @@ export default function AgentTasksTab({ data, reload, session }) {
87
85
  </div>
88
86
 
89
87
  <!-- Stats summary -->
90
- <div class="adm-mem-stats">
91
- <div class="adm-mem-stat">
92
- <span class="adm-mem-stat-value">${num(total)}</span>
93
- <span class="adm-mem-stat-label">${t('dashboard.agentTasksTotal')}</span>
94
- </div>
95
- </div>
88
+ <${StatsGrid} items=${[{ label: t('dashboard.agentTasksTotal'), value: total }]} />
96
89
 
97
90
  <!-- Table -->
98
91
  ${tasks.length === 0 && !loading && html`<${Empty} text=${t('dashboard.agentTasksEmpty')} />`}
@@ -115,7 +108,7 @@ export default function AgentTasksTab({ data, reload, session }) {
115
108
  <td class="mono" style="font-size:.8rem">${escHtml(task.agent_gaii)}</td>
116
109
  <td>${escHtml(task.title)}</td>
117
110
  <td>
118
- <span class="tag" style="color:${statusColor(task.status)};border-color:${statusColor(task.status)}">
111
+ <span class=${statusTagClass(task.status)}>
119
112
  ${task.status}
120
113
  </span>
121
114
  </td>
@@ -176,7 +176,7 @@ export default function ChatInstancesTab({ data, reload }) {
176
176
 
177
177
  <!-- AI Chat Instances -->
178
178
  <${StatsGrid} items=${[
179
- { label: t('dashboard.totalSessions'), value: sessions.length, color: '#06b6d4' },
179
+ { label: t('dashboard.totalSessions'), value: sessions.length, tone: 'cyan' },
180
180
  ]} />
181
181
 
182
182
  ${!sessions.length
@@ -10,6 +10,9 @@
10
10
  * v1.2.0 -- 2026-05-31 -- Fix: inputs were bound to the saved value, not the
11
11
  * pending edit, so toggling a checkbox / typing snapped straight back on
12
12
  * re-render (looked un-editable). All inputs now reflect pending[path].
13
+ * v1.3.0 -- 2026-06-02 -- Admin design unification: inline error <div>
14
+ * replaced with shared <ErrorBox>; success message moved to the
15
+ * adm-config-result-ok class (no inline color/background styles).
13
16
  */
14
17
  import { h } from 'preact';
15
18
  import { useState } from 'preact/hooks';
@@ -17,7 +20,7 @@ import htm from 'htm';
17
20
  const html = htm.bind(h);
18
21
  import { t } from '/js/i18n.js';
19
22
  import { escHtml } from '/js/utils.js';
20
- import { Badge, Empty, ExpandableHelp } from './shared.js';
23
+ import { Badge, Empty, ExpandableHelp, ErrorBox } from './shared.js';
21
24
  import { saveConfig, deleteConfig } from '/js/services/admin.js';
22
25
 
23
26
  // Map config source to Badge type for visual distinction
@@ -113,7 +116,9 @@ export default function ConfigTab({ data, reload }) {
113
116
  </div>
114
117
  `}
115
118
 
116
- ${result && html`<div class="adm-mb-md adm-text-base" style="padding:8px 12px;border-radius:6px;background:${result.ok ? '#16a34a22' : '#dc262622'};color:${result.ok ? '#22c55e' : '#ef4444'}">${escHtml(result.msg)}</div>`}
119
+ ${result && (result.ok
120
+ ? html`<div class="adm-config-result-ok adm-mb-md adm-text-base">${escHtml(result.msg)}</div>`
121
+ : html`<div class="adm-mb-md"><${ErrorBox} message=${result.msg} /></div>`)}
117
122
 
118
123
  <!-- Pending changes banner -->
119
124
  ${editable && pendingKeys.length > 0 && html`
@@ -5,6 +5,11 @@
5
5
  * and shows component breakdowns.
6
6
  * @version-history
7
7
  * v1.0.0 — 2026-03-17 — Initial admin cortex management tab
8
+ * v1.1.0 — 2026-06-02 — Admin design unification: fix the broken useToast wiring
9
+ * (object-destructured the array-returning hook, and passed wrong props to
10
+ * <Toast> — toasts never rendered); now uses the canonical [msg,showErr,showOk,
11
+ * clear] API with a showToast adapter + correct <Toast type text onDismiss>.
12
+ * Main btn-* classes → adm-btn / adm-btn-action / adm-btn adm-btn-danger-solid.
8
13
  */
9
14
  import { h } from 'preact';
10
15
  import { useState, useEffect, useCallback } from 'preact/hooks';
@@ -19,24 +24,25 @@ import * as cortexService from '/js/services/cortex.js';
19
24
  const COMP_ICONS = { schema: '\u{1F4D0}', prompt: '\u{1F4AC}', action: '\u26A1', 'board-template': '\u{1F4CC}', ontology: '\u{1F9EC}', 'seed-data': '\u{1F331}', lib: '\u{1F4E6}' };
20
25
 
21
26
  export default function CortexTab({ data, reload, session }) {
22
- const { toast, showToast } = useToast();
27
+ const [toast, showErr, showOk, clearToast] = useToast();
28
+ const showToast = (text, isError) => isError ? showErr(text) : showOk(text);
23
29
  const { confirm, ConfirmUI } = useConfirm();
24
30
  const [extensions, setExtensions] = useState([]);
25
31
  const [loading, setLoading] = useState(true);
26
32
  const [detail, setDetail] = useState(null);
27
33
 
28
- const loadData = useCallback(async () => {
29
- setLoading(true);
34
+ const loadData = useCallback(async ({ showSpinner = true } = {}) => {
35
+ if (showSpinner) setLoading(true);
30
36
  try {
31
37
  const resp = await cortexService.listExtensions();
32
38
  setExtensions(resp?.extensions || []);
33
- } catch { setExtensions([]); }
39
+ } catch { if (showSpinner) setExtensions([]); }
34
40
  setLoading(false);
35
41
  }, []);
36
42
 
37
43
  useEffect(() => { loadData(); }, [loadData]);
38
44
  useEffect(() => {
39
- const handler = () => { loadData(); };
45
+ const handler = () => { loadData({ showSpinner: false }); }; // silent: no flash
40
46
  window.addEventListener('aimeat-live-update', handler);
41
47
  return () => window.removeEventListener('aimeat-live-update', handler);
42
48
  }, [loadData]);
@@ -92,9 +98,9 @@ export default function CortexTab({ data, reload, session }) {
92
98
  const isActive = ext.status === 'active';
93
99
  const vis = ext.visibility || 'private';
94
100
  return html`<div>
95
- <${Toast} toast=${toast} />
101
+ ${toast && html`<${Toast} type=${toast.type} text=${toast.text} onDismiss=${clearToast} />`}
96
102
  <${ConfirmUI} />
97
- <button class="btn-outline" onClick=${() => setDetail(null)}>${t('dashboard.cortex.back')}</button>
103
+ <button class="adm-btn-action" onClick=${() => setDetail(null)}>${t('dashboard.cortex.back')}</button>
98
104
  <h3 class="adm-section-title" style="margin-top:1rem">${escHtml(ext.name)} <span class="adm-text-dim adm-text-sm">v${ext.version || '?'}</span></h3>
99
105
  <p class="adm-text-dim">${escHtml(ext.description || '')}</p>
100
106
  <div class="adm-meta-row">
@@ -113,18 +119,18 @@ export default function CortexTab({ data, reload, session }) {
113
119
 
114
120
  <div class="adm-actions-row" style="margin-top:1.5rem">
115
121
  ${isActive
116
- ? html`<button class="btn-outline" onClick=${() => handleDeactivate(ext.name)}>${t('dashboard.cortex.deactivate')}</button>`
117
- : html`<button class="btn-primary" onClick=${() => handleActivate(ext.name)}>${t('dashboard.cortex.activate')}</button>`}
118
- <button class="btn-outline" onClick=${() => handleVisibility(ext.name, vis)}>
122
+ ? html`<button class="adm-btn-action" onClick=${() => handleDeactivate(ext.name)}>${t('dashboard.cortex.deactivate')}</button>`
123
+ : html`<button class="adm-btn" onClick=${() => handleActivate(ext.name)}>${t('dashboard.cortex.activate')}</button>`}
124
+ <button class="adm-btn-action" onClick=${() => handleVisibility(ext.name, vis)}>
119
125
  ${vis === 'public' ? t('dashboard.cortex.makePrivate') : t('dashboard.cortex.makePublic')}
120
126
  </button>
121
- <button class="btn-danger-solid" onClick=${() => handleUninstall(ext.name)}>${t('dashboard.cortex.uninstall')}</button>
127
+ <button class="adm-btn adm-btn-danger-solid" onClick=${() => handleUninstall(ext.name)}>${t('dashboard.cortex.uninstall')}</button>
122
128
  </div>
123
129
  </div>`;
124
130
  }
125
131
 
126
132
  return html`<div>
127
- <${Toast} toast=${toast} />
133
+ ${toast && html`<${Toast} type=${toast.type} text=${toast.text} onDismiss=${clearToast} />`}
128
134
  <${ConfirmUI} />
129
135
  <${StatsGrid} items=${[
130
136
  { label: t('dashboard.cortex.total'), value: extensions.length },
@@ -158,8 +164,8 @@ export default function CortexTab({ data, reload, session }) {
158
164
  <td class="adm-text-dim">${types.join(', ')}</td>
159
165
  <td>
160
166
  ${isActive
161
- ? html`<button class="btn-outline btn-sm" onClick=${() => handleDeactivate(ext.name)}>${t('dashboard.cortex.deactivate')}</button>`
162
- : html`<button class="btn-primary btn-sm" onClick=${() => handleActivate(ext.name)}>${t('dashboard.cortex.activate')}</button>`}
167
+ ? html`<button class="adm-btn-action" onClick=${() => handleDeactivate(ext.name)}>${t('dashboard.cortex.deactivate')}</button>`
168
+ : html`<button class="adm-btn" onClick=${() => handleActivate(ext.name)}>${t('dashboard.cortex.activate')}</button>`}
163
169
  </td>
164
170
  </tr>`;
165
171
  })}
@@ -1,11 +1,23 @@
1
+ /**
2
+ * @file csm-tab.js
3
+ * @description Admin dashboard CSM (Community Service Manifest) tab — list,
4
+ * detail, create and delete of registered CSM templates, plus an AI builder
5
+ * prompt view.
6
+ * @structure default export CsmTab({ data, reload }); list/detail/create/prompt views.
7
+ * @usage Mounted by the admin dashboard tab router.
8
+ * @version-history
9
+ * v1.1.0 — 2026-06-02 — Admin design unification: inline danger styles → adm-btn-danger
10
+ * (2 delete buttons), raw textarea → adm-textarea adm-input-full, error div → <ErrorBox>.
11
+ */
1
12
  import { h } from 'preact';
2
13
  import { useState } from 'preact/hooks';
3
14
  import htm from 'htm';
4
15
  const html = htm.bind(h);
5
16
  import { t } from '/js/i18n.js';
6
17
  import { escHtml } from '/js/utils.js';
7
- import { dt, Empty, ExpandableHelp, useToast, Toast } from './shared.js';
18
+ import { dt, Empty, ExpandableHelp, ErrorBox, useToast, Toast } from './shared.js';
8
19
  import { useConfirm } from '/components/Modal.js';
20
+ import { CopyButton } from '/components/CopyButton.js';
9
21
  import { getCsmDetail, deleteCsm, createCsm, getCsmFileTemplates, getCsmFileTemplate, getCsmBuilderPrompt } from '/js/services/admin.js';
10
22
 
11
23
  export default function CsmTab({ data, reload }) {
@@ -17,7 +29,6 @@ export default function CsmTab({ data, reload }) {
17
29
  const [loading, setLoading] = useState(false);
18
30
  const [err, setErr] = useState('');
19
31
  const [prompt, setPrompt] = useState('');
20
- const [copied, setCopied] = useState(false);
21
32
  const [toast, showErr, showOk, clearToast] = useToast();
22
33
  const { confirm, ConfirmUI } = useConfirm();
23
34
 
@@ -89,15 +100,7 @@ export default function CsmTab({ data, reload }) {
89
100
  setLoading(false);
90
101
  }
91
102
 
92
- async function copyPrompt() {
93
- try {
94
- await navigator.clipboard.writeText(prompt);
95
- setCopied(true);
96
- setTimeout(() => setCopied(false), 2000);
97
- } catch { /* fallback — select text */ }
98
- }
99
-
100
- function backToList() { setView('list'); setDetail(null); setErr(''); setCopied(false); }
103
+ function backToList() { setView('list'); setDetail(null); setErr(''); }
101
104
 
102
105
  // ── Detail view ──
103
106
  if (view === 'detail' && detail) {
@@ -110,7 +113,7 @@ export default function CsmTab({ data, reload }) {
110
113
  <div class="adm-card adm-mt-md">
111
114
  <div class="adm-flex-between">
112
115
  <h4 style="margin:0">${escHtml(detail.name)}</h4>
113
- <button class="adm-btn-sm" style="color:#ef4444;border-color:#ef4444" onClick=${() => doDelete(detail.name)}>
116
+ <button class="adm-btn-sm adm-btn-danger" onClick=${() => doDelete(detail.name)}>
114
117
  ${t('dashboard.delete')}
115
118
  </button>
116
119
  </div>
@@ -149,9 +152,10 @@ export default function CsmTab({ data, reload }) {
149
152
 
150
153
  <div style="position:relative">
151
154
  <pre style="background:var(--bg-deep,#0f172a);padding:16px;border-radius:8px;overflow:auto;font-size:.8rem;max-height:500px;white-space:pre-wrap;line-height:1.5;border:1px solid var(--glass-border,#334155)">${prompt}</pre>
152
- <button class="adm-btn" style="position:absolute;top:8px;right:8px" onClick=${copyPrompt}>
153
- ${copied ? t('dashboard.copied') : t('dashboard.copy')}
154
- </button>
155
+ <span style="position:absolute;top:8px;right:8px">
156
+ <${CopyButton} text=${prompt} className="adm-btn"
157
+ label=${t('dashboard.copy')} copiedLabel=${t('dashboard.copied')} />
158
+ </span>
155
159
  </div>
156
160
 
157
161
  <div class="adm-mt-lg">
@@ -190,13 +194,13 @@ export default function CsmTab({ data, reload }) {
190
194
  <label class="adm-text-base adm-text-dim" style="display:block;margin-bottom:4px">${t('dashboard.csmDefinition')}</label>
191
195
  <textarea
192
196
  rows="16"
193
- style="width:100%;font-family:monospace;font-size:.8rem;resize:vertical"
197
+ class="adm-textarea adm-input-full"
194
198
  placeholder=${t('dashboard.csmYamlPlaceholder')}
195
199
  value=${yaml}
196
200
  onInput=${e => setYaml(e.target.value)}
197
201
  />
198
202
 
199
- ${err && html`<div class="error-box adm-mt-sm">${err}</div>`}
203
+ ${err && html`<div class="adm-mt-sm"><${ErrorBox} message=${err} /></div>`}
200
204
 
201
205
  <div class="adm-flex adm-mt-md">
202
206
  <button class="adm-btn" onClick=${doCreate} disabled=${loading || !yaml.trim()}>
@@ -246,7 +250,7 @@ export default function CsmTab({ data, reload }) {
246
250
  <td style="white-space:nowrap">
247
251
  <button class="adm-btn-sm" onClick=${() => showDetail(c.name)}>${t('dashboard.details')}</button>
248
252
  ${' '}
249
- <button class="adm-btn-sm" style="color:#ef4444;border-color:#ef4444" onClick=${() => doDelete(c.name)}>
253
+ <button class="adm-btn-sm adm-btn-danger" onClick=${() => doDelete(c.name)}>
250
254
  ${t('dashboard.delete')}
251
255
  </button>
252
256
  </td>
@@ -28,9 +28,9 @@ export default function DirectoryTab({ data }) {
28
28
  <${ExpandableHelp} title=${t('dashboard.directoryHelpTitle')}>${t('dashboard.directoryHelpDetail')}</${ExpandableHelp}>
29
29
 
30
30
  <${StatsGrid} items=${[
31
- { label: t('dashboard.totalEntries'), value: dir?.total || 0, color: '#06b6d4' },
32
- { label: t('dashboard.cities'), value: dir?.cities || 0, color: '#22c55e' },
33
- { label: t('dashboard.categories'), value: dir?.categories || 0, color: '#f59e0b' },
31
+ { label: t('dashboard.totalEntries'), value: dir?.total || 0, tone: 'cyan' },
32
+ { label: t('dashboard.cities'), value: dir?.cities || 0, tone: 'green' },
33
+ { label: t('dashboard.categories'), value: dir?.categories || 0, tone: 'amber' },
34
34
  ]} />
35
35
 
36
36
  <div class="adm-card" style="margin-top:12px">
@@ -1,3 +1,13 @@
1
+ /**
2
+ * @file email-tab.js
3
+ * @description Admin dashboard Email tab — SMTP config view, test-send, group
4
+ * broadcast, and per-locale email template editor with AI-prompt generation.
5
+ * @structure EmailTab (default), TemplateEditor, buildAiPrompt
6
+ * @usage Mounted by the admin dashboard tab router.
7
+ * @version-history
8
+ * v1.1.0 — 2026-06-02 — Admin design unification: raw textareas → adm-textarea
9
+ * adm-input-full; inline button color styles → adm-btn-success/adm-btn-danger.
10
+ */
1
11
  import { h } from 'preact';
2
12
  import { useState, useEffect } from 'preact/hooks';
3
13
  import htm from 'htm';
@@ -5,6 +15,7 @@ const html = htm.bind(h);
5
15
  import { t } from '/js/i18n.js';
6
16
  import { EconRow, Empty, ExpandableHelp } from './shared.js';
7
17
  import { useConfirm } from '/components/Modal.js';
18
+ import { CopyButton } from '/components/CopyButton.js';
8
19
  import { sendTestEmail, getEmailTemplates, sendGroupEmail, saveEmailTemplate, resetEmailTemplate, seedEmailTemplates, resetAllEmailTemplates, saveConfig } from '/js/services/admin.js';
9
20
 
10
21
  const TEMPLATE_IDS = ['notification', 'verification', 'magic_link', 'match_suggestion'];
@@ -107,23 +118,6 @@ function TemplateEditor({ tpl, locale, onSave, onReset }) {
107
118
  }, { danger: true });
108
119
  }
109
120
 
110
- async function copyAiPrompt() {
111
- const prompt = buildAiPrompt(tpl, locale);
112
- try {
113
- await navigator.clipboard.writeText(prompt);
114
- setMsg({ ok: true, text: t('dashboard.emailTplAiPromptCopied') });
115
- } catch {
116
- // Fallback: select textarea
117
- const ta = document.createElement('textarea');
118
- ta.value = prompt;
119
- document.body.appendChild(ta);
120
- ta.select();
121
- document.execCommand('copy');
122
- document.body.removeChild(ta);
123
- setMsg({ ok: true, text: t('dashboard.emailTplAiPromptCopied') });
124
- }
125
- }
126
-
127
121
  const hasChanges = editHtml !== (tpl.preview || '') || editText !== (tpl.text || '');
128
122
 
129
123
  const tabStyle = (active) => `
@@ -167,17 +161,19 @@ function TemplateEditor({ tpl, locale, onSave, onReset }) {
167
161
  `}
168
162
  ${view === 'html' && html`
169
163
  <textarea
164
+ class="adm-textarea adm-input-full"
170
165
  value=${editHtml}
171
166
  onInput=${e => setEditHtml(e.target.value)}
172
- style="width:100%;height:420px;font-family:monospace;font-size:12px;padding:10px;border:1px solid var(--glass-border);border-top:none;border-radius:0 0 6px 6px;background:rgba(0,0,0,0.2);color:var(--text-bright);resize:vertical;line-height:1.5;tab-size:2"
167
+ style="height:420px;font-size:12px;border-top:none;border-radius:0 0 6px 6px"
173
168
  spellcheck="false"
174
169
  />
175
170
  `}
176
171
  ${view === 'text' && html`
177
172
  <textarea
173
+ class="adm-textarea adm-input-full"
178
174
  value=${editText}
179
175
  onInput=${e => setEditText(e.target.value)}
180
- style="width:100%;height:300px;font-family:monospace;font-size:13px;padding:10px;border:1px solid var(--glass-border);border-top:none;border-radius:0 0 6px 6px;background:rgba(0,0,0,0.2);color:var(--text-bright);resize:vertical;line-height:1.5"
176
+ style="height:300px;font-size:13px;border-top:none;border-radius:0 0 6px 6px"
181
177
  spellcheck="false"
182
178
  />
183
179
  `}
@@ -186,11 +182,11 @@ function TemplateEditor({ tpl, locale, onSave, onReset }) {
186
182
  <div class="adm-flex-center" style="flex-wrap:wrap;margin-top:10px">
187
183
  <button class="adm-btn-action adm-text-sm" onClick=${doSave} disabled=${saving || !hasChanges}>
188
184
  ${saving ? '...' : t('dashboard.emailTplSave')}</button>
189
- <button class="adm-btn-action adm-text-sm" onClick=${copyAiPrompt}
190
- style="background:rgba(79,70,229,0.1);border-color:rgba(79,70,229,0.3);color:#818cf8">${t('dashboard.emailTplAiPrompt')}</button>
185
+ <${CopyButton} text=${buildAiPrompt(tpl, locale)} className="adm-btn-action adm-text-sm"
186
+ label=${t('dashboard.emailTplAiPrompt')} copiedLabel=${t('dashboard.emailTplAiPrompt')}
187
+ onCopied=${() => setMsg({ ok: true, text: t('dashboard.emailTplAiPromptCopied') })} />
191
188
  ${tpl.isCustom && html`
192
- <button class="adm-btn-action adm-text-sm" onClick=${doReset} disabled=${saving}
193
- style="color:#ef4444;border-color:rgba(239,68,68,0.3)">${t('dashboard.emailTplReset')}</button>
189
+ <button class="adm-btn-action adm-btn-danger adm-text-sm" onClick=${doReset} disabled=${saving}>${t('dashboard.emailTplReset')}</button>
194
190
  `}
195
191
  ${msg && html`<span class="adm-text-sm" style="color:${msg.ok ? '#22c55e' : '#ef4444'}">${msg.text}</span>`}
196
192
  </div>
@@ -385,11 +381,10 @@ export default function EmailTab({ data, reload, locale }) {
385
381
  <input type="text" class="adm-input adm-input-full adm-mb-sm" value=${grpSubject}
386
382
  onInput=${e => setGrpSubject(e.target.value)}
387
383
  placeholder=${t('dashboard.emailGroupSubject')} />
388
- <textarea class="adm-input adm-input-full" value=${grpBody}
384
+ <textarea class="adm-textarea adm-input-full" value=${grpBody}
389
385
  onInput=${e => setGrpBody(e.target.value)}
390
386
  placeholder=${t('dashboard.emailGroupBody')}
391
- rows="4"
392
- style="resize:vertical;font-family:inherit" />
387
+ rows="4" />
393
388
  <div class="adm-mt-sm">
394
389
  <button class="adm-btn" onClick=${doGroupSend} disabled=${grpSending || !grpSubject || !grpBody}>
395
390
  ${grpSending ? '...' : t('dashboard.emailGroupSend')}
@@ -414,12 +409,12 @@ export default function EmailTab({ data, reload, locale }) {
414
409
  <!-- Seed / Reset All buttons -->
415
410
  <div class="adm-flex-center" style="flex-wrap:wrap;margin-bottom:14px;padding:10px 12px;border-radius:6px;background:rgba(255,255,255,0.02);border:1px solid var(--glass-border)">
416
411
  ${!seeded && html`
417
- <button class="adm-btn-action adm-text-sm" style="background:rgba(34,197,94,0.1);border-color:rgba(34,197,94,0.3);color:#22c55e"
412
+ <button class="adm-btn-action adm-btn-success adm-text-sm"
418
413
  onClick=${doSeedDefaults}>${t('dashboard.emailTplSeedDefaults')}</button>
419
414
  <span style="font-size:.78rem" class="adm-text-dim">${t('dashboard.emailTplSeedExplain')}</span>
420
415
  `}
421
416
  ${seeded && html`
422
- <button class="adm-btn-action adm-text-sm" style="color:#ef4444;border-color:rgba(239,68,68,0.3)"
417
+ <button class="adm-btn-action adm-btn-danger adm-text-sm"
423
418
  onClick=${doResetAll}>${t('dashboard.emailTplResetAll')}</button>
424
419
  <button class="adm-btn-action adm-text-sm"
425
420
  onClick=${doSeedDefaults}>${t('dashboard.emailTplReseed')}</button>
@@ -1,3 +1,13 @@
1
+ /**
2
+ * @file federation-tab.js
3
+ * @description Admin dashboard Federation tab — peer management, peering
4
+ * requests, network directory, auth policy, and federation bus overview.
5
+ * @structure FederationTab (default)
6
+ * @usage Mounted by the admin dashboard tab router.
7
+ * @version-history
8
+ * v1.1.0 — 2026-06-02 — Admin design unification: inline button color style
9
+ * on the approve action → adm-btn-success.
10
+ */
1
11
  import { h } from 'preact';
2
12
  import { useState, useEffect, useCallback, useRef } from 'preact/hooks';
3
13
  import htm from 'htm';
@@ -245,10 +255,10 @@ export default function FederationTab({ data, reload }) {
245
255
 
246
256
  <!-- ═══ Stats Overview ═══ -->
247
257
  <${StatsGrid} items=${[
248
- { label: t('dashboard.fedActivePeers'), value: activePeers.length, color: '#22c55e' },
249
- { label: t('dashboard.fedDegradedPeers'), value: degradedPeers.length, color: '#f59e0b' },
250
- { label: t('dashboard.fedOfflinePeers'), value: offlinePeers.length, color: '#ef4444' },
251
- { label: t('dashboard.fedPendingRequests'), value: pendingRequests.length, color: '#06b6d4' },
258
+ { label: t('dashboard.fedActivePeers'), value: activePeers.length, tone: 'green' },
259
+ { label: t('dashboard.fedDegradedPeers'), value: degradedPeers.length, tone: 'amber' },
260
+ { label: t('dashboard.fedOfflinePeers'), value: offlinePeers.length, tone: 'red' },
261
+ { label: t('dashboard.fedPendingRequests'), value: pendingRequests.length, tone: 'cyan' },
252
262
  ]} />
253
263
 
254
264
  <!-- ═══ Federation Auth Policy ═══ -->
@@ -331,7 +341,7 @@ export default function FederationTab({ data, reload }) {
331
341
  <td style="max-width:200px;overflow:hidden;text-overflow:ellipsis">${escHtml(r.message || '\u2014')}</td>
332
342
  <td class="adm-text-dim">${dt(r.created_at)}</td>
333
343
  <td style="display:flex;gap:4px">
334
- <button class="adm-btn-sm" style="color:#22c55e" onClick=${() => doApprove(r.id)}>\u2714 ${t('dashboard.approve')}</button>
344
+ <button class="adm-btn-sm adm-btn-success" onClick=${() => doApprove(r.id)}>\u2714 ${t('dashboard.approve')}</button>
335
345
  <button class="adm-btn-sm adm-text-error" onClick=${() => doReject(r.id)}>\u2718 ${t('dashboard.fedReject')}</button>
336
346
  </td>
337
347
  </tr>`)}